// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using CIP4.Common;

namespace CIP4.RulesEngine
{
    public abstract partial class AtomType //: CIValueType
    {
        // This is used as a regular expression to locate unbound variables in AtomNames - 
        // AtomNames can contain either NamedVariable names or Scripts - the name construction
        // is the same in either case.
        public const string UNBOUNDVARIABLEPATTERN = "unbound variable|this|item|each";

        public const string UNBOUNDVARIABLELITERAL = "item";

        public const string UNBOUNDPROPERTYLITERAL = "this";

        protected bool m_IsCompiled;

        protected string m_FunctionName;

        public virtual object AtomObjectValue
        {
            get { throw new NotImplementedException(); }
        }

        public AtomSourceEnum AtomSource { get; set; }

        public string AtomName { get; set; }

        private string m_AtomFunctionName;

        protected string AtomFunctionName
        {
            get 
            {
                if (m_AtomFunctionName == null)
                    m_AtomFunctionName = "f" + Guid.NewGuid().ToString("N");
                return m_AtomFunctionName; 
            }
        }

        public override bool IsLiteral
        {
            get { return AtomSource == AtomSourceEnum.literal; }
        }

        public override bool IsNamedValue
        {
            get { return AtomSource == AtomSourceEnum.namedValue; }
        }

        protected AtomType()
        {
        }

        protected AtomType(AtomSourceEnum atomSource)
        {
            AtomSource = atomSource;
        }

        protected AtomType(AtomSourceEnum atomSource, string atomName)
        {
            AtomSource = atomSource;
            AtomName = atomName;
        }

        protected AtomType(XElement element)
        {
            XAttribute sourceAtt = element.Attribute("AtomSource");
            if (sourceAtt != null)
                AtomSource = AtomSourceEnumFromString(sourceAtt.Value);
            XAttribute nameAtt = element.Attribute("Name");
            if (nameAtt != null)
                AtomName = nameAtt.Value;
        }

        public CIValueType Assign(CIValueType value, IDataPacketType data)
        {
            if (AtomSource == AtomSourceEnum.literal)
            {
                throw new ArgumentException(string.Format("Cannot assign {0} to {1} because {1} is a literal value", value, this));
            }
            if (AtomSource == AtomSourceEnum.function)
            {
                if (data.SetValue == null)
                    throw new ArgumentException("Rule references a get value function but none was supplied - you must provide a function for the Execute Data parameter's GetValue function");
                data.SetValue(AtomName, value.Evaluate(data).ToString(), data);
                return value;
            }
            if (AtomSource == AtomSourceEnum.parameter)
            {
                if (data.Parameters.ContainsKey(AtomName))
                    data.Parameters[AtomName] = value.Evaluate(data);
                else
                    data.Parameters.Add(AtomName, value.Evaluate(data));
                return value;
            }
            if (AtomSource == AtomSourceEnum.namedValue)
            {
                // This approach only works for simple sv properties - ultimately will need some
                // way to define a path to compound mv properties at which point 'Replace("Destination.", "")'
                // gets much more complicated - most likely ValuesByName will have to understand a path
                // expression and be responsible for returning the proeprty to which the assignment must be made. 
                // One could imagine something like Destination.Address.Lines[0] - Hopefully ValuesByName
                // will not have to understand anything about Address other than it's name and the fact 
                // that it is MV. At all costs, should try to avoid ValuesByname needing to interpret the type.
                throw new NotImplementedException();
            }
            throw new ArgumentException(string.Format("The rule is not properly formed, {0} has an unknown source type", ToString()));
        }

        public bool Remove(IDataPacketType data)
        {
            if (AtomSource == AtomSourceEnum.literal)
            {
                throw new ArgumentException(string.Format("Cannot remove {0} because {0} is a literal value", ToString()));
            }
            if (AtomSource == AtomSourceEnum.function)
            {
                throw new ArgumentException(string.Format("Cannot remove {0} because {0} is a function value",
                                                          ToString()));
            }
            if (AtomSource == AtomSourceEnum.parameter)
            {
                if (data.Parameters.ContainsKey(AtomName))
                    return data.Parameters.Remove(AtomName);
            }
            else if (AtomSource == AtomSourceEnum.namedValue)
            {
                throw new NotImplementedException();
            }
            throw new ArgumentException(string.Format("The rule is not properly formed, {0} has an unknown source type", ToString()));
        }

        public override object Evaluate(IDataPacketType data)
        {
            if (AtomSource == AtomSourceEnum.literal)
            {
                return AtomObjectValue == null ? null : AtomObjectValue.ToString();
            }
            if (AtomSource == AtomSourceEnum.namedValue)
            {
                return data.GetNamedValue(AtomName);
            }
            if (AtomSource == AtomSourceEnum.parameter)
            {
                if (data.Parameters.ContainsKey(AtomName))
                    return data.Parameters[AtomName].ToString();
                return "";
            }
            if (AtomSource == AtomSourceEnum.function)
            {
                if (data.GetValue == null)
                    throw new ArgumentException("Rule references a get value function but none was supplied - you must provide a function for the Execute Data parameter's GetValue function");
                return data.GetValue(AtomName, data);
            }
            throw new ApplicationException("Invalid rule unknown Atom type encountered");
        }

        public override bool Check(IDataPacketType data)
        {
            bool result = true;

            LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
            {
                if (status == MajorEnum.Error)
                    result = false;
                return data.Logger(status, minorStatus, message, paras);
            };

            if ((AtomSource == AtomSourceEnum.function || 
                AtomSource == AtomSourceEnum.namedValue ||
                AtomSource == AtomSourceEnum.parameter) && 
                string.IsNullOrEmpty(AtomName))
                localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "Atoms of type {0} must have a name: {0}", new object[] { AtomSource.ToString(), ToString() });
            else if (AtomSource == AtomSourceEnum.variable)
            {
                if (AtomName == null)
                    localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "Literal must have a name", new object[] { ToString() });
                else
                {
                    // Name must be a valid XPath expression - run it against the CI_XmlData node
                    // to at least verify that it compiles
                    try
                    {
                        XmlDocument doc = new XmlDocument();
                        XmlNodeList nl = doc.SelectNodes(AtomName);
                        if (nl != null && nl.Count > 0)
                            goto next;
                    }
                    catch (System.Xml.XPath.XPathException ex)
                    {
                        localLogger(MajorEnum.Error, MinorEnum.ExpressionStructure, "AtomName for a variable name must be a valid XPath expression in {1}: {0} ", new object[] { ex.Message, ToString() });
                    }
                }
            next: ;
            }
            return result;
        }

        protected void XmlFor(XmlWriter xwrtr)
        {
            if (!string.IsNullOrEmpty(AtomName))
                xwrtr.WriteAttributeString("Name", AtomName);
            xwrtr.WriteAttributeString("AtomSource", AtomSource.ToString());
        }

        /// <summary>
        /// For an explanation of how to use this see the comment on ActionType.ResolveName
        /// </summary>
        /// <param name="qualifiedName"></param>
        public override void ResolveName(string qualifiedName)
        {
            if (string.IsNullOrEmpty(AtomName))
                return;
            Regex rx = AtomSource == AtomSourceEnum.namedValue?
                new Regex("^(" + UNBOUNDVARIABLEPATTERN + ")"):
                new Regex(UNBOUNDVARIABLEPATTERN);
            Match mtch = rx.Match(AtomName);
            if (AtomSource == AtomSourceEnum.namedValue && mtch.Success)
            {
                AtomName = qualifiedName + AtomName.Substring(mtch.Length);
            }
            else if (!string.IsNullOrEmpty(AtomName) && mtch.Success && !string.IsNullOrEmpty(qualifiedName))
            {
                AtomName = rx.Replace(AtomName, qualifiedName);
            }
        }
    }
}
